# -*- coding:utf-8 -*-

__author__ = 'shouke'

import os
import re
import traceback
import configparser
import xmind

from datetime import datetime
from openpyxl import Workbook, load_workbook
from openpyxl.styles import Font, Alignment

from utils.file_dir_util import FileDirUitl
from utils.log import logger
from utils.zen_tao import ZenTaoCli

CASE_STAGE_MAP = {
    '单元测试阶段': 'unittest',
    '单元测试': 'unittest',
    '功能测试阶段': 'feature',
    '功能测试': 'feature',
    '集成测试阶段': 'intergrate',
    '集成测试': 'intergrate',
    '系统测试阶段': 'system',
    '系统测试': 'system',
    '冒烟测试阶段': 'smoke',
    '冒烟测试': 'smoke',
    '版本验证阶段': 'bvt',
    '版本验证': 'bvt'
}

CASE_STAGE_ALIAS_MAP = {
    '单元测试': '单元测试阶段',
    '功能测试': '功能测试阶段',
    '集成测试': '集成测试阶段',
    '系统测试': '系统测试阶段',
    '冒烟测试': '冒烟测试阶段',
    '版本验证': '版本验证阶段'
}
CASE_TYPE_MAP = {
    '功能测试': 'feature',
    '接口测试': 'interface',
    '性能测试': 'performance',
    '安装部署': 'install',
    '配置测试': 'config',
    '安全相关': 'security',
    '其它': 'other'
}

ZENTAO_TESTCASE_FIELD_MAP = {
    '用例编号': 'case_id'
}


class TestcaseConverter():
    def __init__(self):
        try:
            config = configparser.ConfigParser()
            application_conf_file_path = './conf/application.conf'
            if os.getenv('TESTCASE_HELPER_DIR'):
                application_conf_file_path = os.path.join(os.environ['TESTCASE_HELPER_DIR'], 'conf/application.conf')
            config.read(application_conf_file_path, encoding='utf-8')
            common_config = config['COMMON']
            xmind_to_excel_config = config['XMIND_TO_EXCEL']
            xmind_to_zentao_config = config['XMIND_TO_ZENTAO']
            xmind_to_excel_or_zentao_config = config['XMIND_TO_EXCEL_OR_ZENTAO']
            excel_to_xmind_config = config['EXCEL_TO_XMIND']
            zen_tao_config = config['ZEN_TAO']
        except Exception as e:
            logger.error('初始化用例转换器出错:%s' % traceback.format_exc())
            exit()

        self.result_file_dir_path = self._get_testcase_file_dir_path(common_config.get('result_file_dir_path'),
                                                                     'results')
        if not os.path.exists(self.result_file_dir_path):
            FileDirUitl.mkdirs(self.result_file_dir_path)
        self.digit_no_pattern = re.compile('^\d+?[、|.|,|，| |)]\s*')
        self.remove_case_flag_pattern = re.compile('([（|(]\s*待删除\s*[)|）]|[-| ]\s*待删除)$')
        self.zentao_excel_case_digit_no_pattern = re.compile('^\d+\. .*') # 禅道导出excel用例 步骤，预期等单元格中数字编号匹配模式
        self.case_module_id_pattern = re.compile('\(\s*\#\d+\s*\)') # 用例模块ID匹配模式
        self.excel_testcase_file_parsed = False # 是否执行了excel用例文件解析操作

        self.operation = common_config.get('operation')

        if self.operation == 'excel_to_xmind':
            self.excel_testcase_file_dir_path = self._get_testcase_file_dir_path(excel_to_xmind_config.get('excel_testcase_file_dir_path'), 'excel_testcases')
            self.saving_way = int(excel_to_xmind_config.get('saving_way', '1'))
            self.traverse_and_read_excel_files(self.excel_testcase_file_dir_path)
        else:
            self.xmind_testcase_file_dir_path = self._get_testcase_file_dir_path(xmind_to_excel_or_zentao_config.get('xmind_testcase_file_dir_path'), 'xmind_testcases')
            self.default_execution_stages = xmind_to_excel_or_zentao_config.get('default_applicable_stage', '系统测试阶段')
            self.default_testcase_type = xmind_to_excel_or_zentao_config.get('default_testcase_type', '')
            self.priority = int(xmind_to_excel_or_zentao_config.get('priority', '2'))
            self.default_testcase_status = xmind_to_excel_config.get('default_testcase_status', '')

            self.zentao_cli = None
            if self.operation == 'xmind_to_zentao':
                # auto_create_product = int(xmind_to_zentao_config.get('auto_create_product', '0'))
                auto_create_product = 0
                auto_create_module = int(xmind_to_zentao_config.get('auto_create_module', '0'))
                is_version_pro = int(zen_tao_config.get('is_version_pro', '0'))
                self.zentao_cli = ZenTaoCli(auto_create_product, auto_create_module, is_version_pro)
            else:
                self.testcase_module_adapt_to_zen = xmind_to_excel_config.get('testcase_module_adapt_to_zen', '0')
                self.testcase_module_adapt_to_zen = int(self.testcase_module_adapt_to_zen)
                self.add_path_to_testcase = xmind_to_excel_config.get('add_path_to_testcase')
                self.saving_way = int(xmind_to_excel_config.get('saving_way', '1'))

                if self.saving_way not in [1, 2, 3]:
                    logger.error('保存方式配置错误，可选值：1、2、3')
                    exit()
            modification_time_record_file_path = os.path.join(self.xmind_testcase_file_dir_path, 'file_modification_time_record .txt')
            self.file_modification_time_records = {}
            if os.path.exists(modification_time_record_file_path):
                with open(modification_time_record_file_path, 'r', encoding='utf-8') as f:
                    for line in f:
                        file_path, modification_time = line.strip().split(',')
                        self.file_modification_time_records[file_path.strip()] = modification_time.strip()

            with open(modification_time_record_file_path, 'w', encoding='utf-8') as f:
                file_modification_time_records = []
                self.traverse_and_read_xmind_files(self.xmind_testcase_file_dir_path, f, file_modification_time_records)
                f.write('\n'.join(file_modification_time_records))
            if self.zentao_cli:
                self.zentao_cli.close()

    def _get_testcase_file_dir_path(self, testcase_file_dir_path, default_dirpath):
        testcase_file_dir_path = testcase_file_dir_path.strip()
        if testcase_file_dir_path and not os.path.exists(testcase_file_dir_path):
            FileDirUitl.mkdirs(testcase_file_dir_path)
        if not testcase_file_dir_path:
            dir_path = os.path.split(os.path.abspath(__file__))[0]
            if os.getenv('TESTCASE_HELPER_DIR'):
                dir_path = os.environ['TESTCASE_HELPER_DIR']
            testcase_file_dir_path = os.path.join(dir_path, default_dirpath)
        return testcase_file_dir_path

    def traverse_and_read_xmind_files(self, dir_path, file_handler, file_modification_time_records):
        '''遍历指定目录，读取xmind用例文件'''

        if os.path.isfile(dir_path):
            if not dir_path.endswith('.xmind'):
                return

            stat_info = os.stat(dir_path)
            st_mtime = str(stat_info.st_mtime)
            temp_file_path = dir_path.lower()
            file_modification_time_records.append(f'{temp_file_path},{st_mtime}')
            if temp_file_path in self.file_modification_time_records:
                if self.operation == 'xmind_to_zentao' and st_mtime == self.file_modification_time_records[temp_file_path]:
                    print(f'{temp_file_path} 文件未做修改，已忽略')
                    return

            self.read_xmind_testcase_file(dir_path)
            return
        for root, dirs, files in os.walk(dir_path):
            for file in files:
                if not file.endswith('.xmind'):
                    continue
                path = os.path.join(root, file)
                path = os.path.normcase(path)

                if os.path.isfile(path):
                    stat_info = os.stat(path)
                    st_mtime = str(stat_info.st_mtime)
                    relative_file_path = path.replace(dir_path.lower(), '').strip('\\')
                    file_modification_time_records.append(f'{relative_file_path},{st_mtime}')

                    if relative_file_path in self.file_modification_time_records:
                        if self.operation == 'xmind_to_zentao' and st_mtime == self.file_modification_time_records[
                            relative_file_path]:
                            continue

                    self.read_xmind_testcase_file(path)
                else:
                    self.traverse_and_read_xmind_files(path, file_handler, file_modification_time_records)

    def traverse_and_read_excel_files(self, dir_path):
        '''遍历指定目录，读取excel用例文件'''

        workbook = None
        xmind_sheet = None
        xmind_node_path_map = {} # xmind 节点和节点路径的映射关系
        if self.saving_way in [1, 3]: # 1 所有 excel 对应一个xmind ，每个画布对应一个excel 3 所有 excel 对应一个xmind 画布
            result_file_path = os.path.join(self.result_file_dir_path, 'testcases-summary-' + datetime.now().strftime('%Y%m%d-%H%M%S%f') + '.xmind')

            workbook = xmind.load(result_file_path)
            xmind_sheet = workbook.getPrimarySheet()

        def traverse_and_read_files(dir_path, workbook, xmind_sheet, xmind_node_path_map):
            if os.path.isfile(dir_path):
                if not (dir_path.endswith('.xls') and dir_path.endswith('.xlsx')):
                    return
                self.excel_testcase_file_parsed = True
                if self.saving_way == 2: # 每个excel对应一个xmind，每个xmind只包含一个画布
                    result_file_path = os.path.join(self.result_file_dir_path, os.path.splitext(os.path.split(dir_path)[1])[0])
                    result_file_path ='%s-%s.%s' % (result_file_path, datetime.now().strftime('%Y%m%d-%H%M%S%f'), 'xmind')
                    workbook = xmind.load(result_file_path)
                    xmind_sheet = workbook.getPrimarySheet()
                    xmind_node_path_map = {}
                elif self.saving_way ==  1:
                    xmind_sheet = workbook.createSheet()
                    xmind_node_path_map = {}
                self.read_excel_testcase_file(dir_path, xmind_sheet, xmind_node_path_map)
                if self.saving_way == 2:
                    xmind.save(workbook=workbook, except_revisions=True)
                return

            for root, dirs, files in os.walk(dir_path):
                first_file = True
                for file in files:
                    file_path = os.path.join(root, file)
                    file_path = os.path.normcase(file_path)
                    if os.path.isfile(file_path):
                        if not (file_path.endswith('.xls') or file_path.endswith('.xlsx')) or file.startswith('~$'):
                            continue
                        self.excel_testcase_file_parsed = True
                        if self.saving_way == 2: # 每个excel对应一个xmind，每个xmind只包含一个画布
                            result_file_path = os.path.join(self.result_file_dir_path, os.path.splitext(os.path.split(file_path)[1])[0])
                            result_file_path ='%s-%s.%s' % (result_file_path, datetime.now().strftime('%Y%m%d-%H%M%S%f'), 'xmind')
                            workbook = xmind.load(result_file_path)
                            xmind_sheet = workbook.getPrimarySheet()
                            xmind_node_path_map = {}
                        elif self.saving_way == 1 and not first_file:
                            xmind_sheet = workbook.createSheet()
                            xmind_node_path_map = {}
                        self.read_excel_testcase_file(file_path, xmind_sheet, xmind_node_path_map)
                        first_file = False
                        if self.saving_way == 2:
                            xmind.save(workbook=workbook, except_revisions=True)

                    else:
                        self.traverse_and_read_files(file_path, workbook, xmind_sheet, xmind_node_path_map)

        traverse_and_read_files(dir_path, workbook, xmind_sheet, xmind_node_path_map)
        if workbook and self.excel_testcase_file_parsed:
            xmind.save(workbook=workbook, except_revisions=True)

    def read_xmind_testcase_file(self, file_path):
        '''读取并解析xmind用例文件'''

        if not file_path.endswith('.xmind'):
            return


        logger.info(f'正在读取xmind测试用例文件：{file_path}')

        time_str = datetime.now().strftime('%Y%m%d-%H%M%S%f')
        file_name_suffix = time_str + '.xlsx'

        result_file_name_with_path = os.path.join(self.result_file_dir_path, os.path.split(file_path)[1].replace('.xmind', ''))

        sheet_data_list = []  # [{'sheet_name':'xxxx', 'root_suite_path': '/xxx/xxx', 'root_topic_data': root_topic_data_dict}]
        workbook = xmind.load(file_path)

        excel_sheet = None
        excel_work_book = None
        if self.operation == 'xmind_to_excel' and self.saving_way in [1, 3]:
            excel_work_book = Workbook()
            excel_sheet = None

        for sheet in workbook.getSheets():
            sheet_data_dict  = sheet.getData()
            sheet_name = sheet_data_dict.get('title')

            root_topic_data_dict = sheet_data_dict.get('topic')
            if root_topic_data_dict:
                topic_title = root_topic_data_dict.get('title') or ''
                topic_title = topic_title.rstrip('/')
                temp_list = topic_title.split('-')
                root_suite_path = '' # 根测试集路径
                for item in temp_list:
                    item = item.strip()
                    if item:
                        root_suite_path += item + '/'
                root_suite_path = root_suite_path.rstrip('/')

                if self.operation == 'xmind_to_excel':
                    if self.saving_way == 1: # 每个 xmind 画布对应一个excel Sheet
                        if excel_sheet is None:
                            excel_sheet = excel_work_book.active
                            excel_sheet.title = sheet_name
                        else:
                            excel_sheet = excel_work_book.create_sheet(title = sheet_name)
                        self.init_excel_sheet(excel_sheet)

                        self.parse_xmind_sheets_testcases([
                            {'sheet_name':sheet_name,
                             'root_suite_path': root_suite_path,
                             'root_topic_data': root_topic_data_dict}],
                            excel_sheet)
                    elif self.saving_way == 2: # 每个 xmind 画布对应一个excel
                        work_book = Workbook()
                        excel_sheet = work_book.active
                        excel_sheet.title = sheet_name
                        self.init_excel_sheet(excel_sheet)
                        self.parse_xmind_sheets_testcases([
                            {'sheet_name': sheet_name,
                             'root_suite_path': root_suite_path,
                             'root_topic_data': root_topic_data_dict}],
                            excel_sheet)
                        work_book.save(f'{result_file_name_with_path}-{sheet_name}{file_name_suffix}')
                    elif self.saving_way == 3: # # 所有xmind画布对应一个excel Sheet
                        sheet_data_list.append({'sheet_name':sheet_name, 'root_suite_path': root_suite_path, 'root_topic_data': root_topic_data_dict})
                elif self.operation == 'xmind_to_zentao':
                    self.parse_xmind_sheets_testcases([
                        {'sheet_name': sheet_name,
                         'root_suite_path': root_suite_path,
                         'root_topic_data': root_topic_data_dict}],
                        None)
            else:
                logger.error(f'转换所在xmind画布用例失败：{sheet_name} 失败用例：所有用例，失败原因：未读取到测试用例数据')
                continue

        if sheet_data_list:
            if self.saving_way == 3:  # 所有xmind画布对应一个excel Sheet
                excel_sheet = excel_work_book.active
                excel_sheet.title = '测试用例'
                self.init_excel_sheet(excel_sheet)
                self.parse_xmind_sheets_testcases(sheet_data_list, excel_sheet)
        if excel_work_book:
            file_name_suffix = time_str + '.xlsx'
            result_file_path = f'{result_file_name_with_path}{file_name_suffix}'
            excel_work_book.save(result_file_path)


    def init_excel_sheet(self, excel_sheet):
        '''初始化excel 表单内容'''

        col_width_map = {'A': 40, # 所属模块
                         'B': 20, # 相关研发需求
                         'C': 40, # 用例标题
                         'D': 30, # 前置条件
                         'E': 40,  # 步骤
                         'F': 40, # 预期
                         'G': 10, # 关键词
                         'H': 10, # 优先级
                         'I': 10, # 用例类型
                         'J': 10, # 适用阶段
                         'K': 10} # 用例状态
        for col, width in col_width_map.items():
            excel_sheet.column_dimensions[col].width = width

        # 添加表头
        row = ['所属模块', '相关研发需求', '用例标题', '前置条件', '步骤', '预期',  '关键词', '优先级', '用例类型', '适用阶段', '用例状态']
        excel_sheet.append(row)

        ord_a = ord('A')
        for item in range(ord_a, ord_a + len(row) + 1):
            col = chr(item)
            cell = col + '1'
            excel_sheet[cell].font = Font(bold=True)
            excel_sheet[cell].alignment = Alignment(horizontal="center", vertical="center")


    def parse_xmind_sheets_testcases(self, sheet_data_list, excel_sheet=None):
        '''解析xmind测试用例(解析多个画布)'''

        for sheet_data in sheet_data_list:
            sheet_name = sheet_data.get('sheet_name')
            root_suite_path = sheet_data.get('root_suite_path')
            logger.info(f'正在解析xmind画布({sheet_name})测试用例')

            self.parse_xmind_sheet_testcases(sheet_data.get('root_topic_data'), root_suite_path, {}, excel_sheet)


    def remove_digit_no_prefix(self, content):
        '''去除content开头数字编号'''

        match_result = self.digit_no_pattern.findall(content)
        if match_result:
            content = content[len(match_result[0]):].strip()

        return content

    def parse_pre_conditions_nodes(self, node_title, sub_topic_data):
        '''解析前提条件节点'''
        
        if not node_title:
            return []
        
        if node_title[4:].startswith(':') or node_title[4:].startswith('：'):
            precondition = node_title[5:].strip()
        elif node_title[2:].startswith(':') or node_title[2:].startswith('：'):
            precondition = node_title[3:].strip()
        elif node_title.startswith('前置条件') or node_title.startswith('前提条件'):
            precondition = node_title[4:].strip()
        else:
            precondition = node_title[2:].strip()
        
        sub_preconditions = []
        if sub_topic_data:
            for precondition_topic in sub_topic_data:
                node_title = (precondition_topic.get('title') or '').strip()
                node_title = self.remove_digit_no_prefix(node_title)
                if node_title:
                    sub_preconditions.append(f'{node_title}')
        if sub_preconditions:  # 如果多个前提条件放一个子主题下，忽略子主题名称内容
            return sub_preconditions
        elif precondition:
            return [precondition]
        else:
            return []
    
    def parse_remark_nodes(self, node_title, sub_topic_data):
        '''解析备注节点'''
        
        if not node_title:
            return []
   
        if node_title[2:].startswith(':') or node_title[2:].startswith('：'):
            remark = node_title[3:].strip()
        else:
            remark = node_title[2:].strip()
        
        sub_remarks = []
        if sub_topic_data:
            for remakr_topic in sub_topic_data:
                node_title = (remakr_topic.get('title') or '').strip()
                node_title = self.remove_digit_no_prefix(node_title)
                if node_title:
                    sub_remarks.append(f'{node_title}')
        if sub_remarks:  # 如果多个前提条件放一个子主题下，忽略子主题名称内容
            return sub_remarks
        elif remark:
            return [remark]
        else:
            return []
    def parse_xmind_sheet_testcases(self, topic_data, root_suite_path, topic_id_path_map, excel_sheet, father_topic_id=None, common_pre_conditions=[], common_notes=[], common_stages=''):
        '''
        解析xmind测试用例并存储为excel(解析单个画布)
        @:param topic_id_path_map 主题id和主题所在路径的映射关系
        '''

        topic_id = topic_data.get('id')
        topic_markers = topic_data.get('markers')
        # topic_label = topic_data.get('label').strip()
        topic_title = (topic_data.get('title') or '').strip()
        topic_comment = (topic_data.get('note') or '').strip()

        testcase_steps = []
        sub_topic_data_list = topic_data.get('topics', [])
        if not father_topic_id and not sub_topic_data_list:
            return

        if not sub_topic_data_list or 'priority-' in ''.join(topic_markers):  # 当前主题为最后一个主题、有优先级标记的主题当作测试用例对待
            preconditions_list = []
            stages = ''
            if sub_topic_data_list:  # 存在用例步骤
                for action_topic in sub_topic_data_list:
                    node_title = action_topic.get('title', '').strip()
                    if not node_title:
                        continue
                    node_title = self.remove_digit_no_prefix(node_title)
                    if node_title.startswith('前置条件') or node_title.startswith('前提条件') or node_title.startswith('前提'):
                        preconditions_list.extend(self.parse_pre_conditions_nodes(node_title, action_topic.get('topics')))
                        continue
                    
                    if node_title.startswith('适用阶段'):
                        if node_title[4:].startswith(':') or node_title[4:].startswith('：'):
                            stages = node_title[5:]
                        else:
                            stages = node_title[4:]
                        stages = stages.strip().replace(' ', '').replace('，', ',')
                        continue

                    action = node_title
                    expection_topic_list = action_topic.get('topics')
                    expections_list = []
                    if expection_topic_list:
                        for expection_topic in expection_topic_list:
                            node_title = expection_topic.get('title').strip()
                            node_title = self.remove_digit_no_prefix(node_title)
                            if node_title:
                                expections_list.append(f'{node_title}')
                    testcase_steps.append({'action': action, 'expections': expections_list})

                # case_path = topic_id_path_map.get(father_topic_id) + '/' + topic_title
                case_path = topic_id_path_map.get(father_topic_id)
                topic_id_path_map[topic_id] = case_path
            else:
                # case_path = topic_id_path_map.get(father_topic_id) + '/' + topic_title
                case_path = topic_id_path_map.get(father_topic_id)
            if not case_path:
                logger.error(f'用例{topic_title}路径为空')
                return
            if not topic_title:
                logger.error('用例名称为空')
                return
            if case_path.count('/') < 1:
                logger.error('用例路径只包含根测试集,用例路径至少要包含1个子测试集')
                return

            res = self.remove_case_flag_pattern.findall(topic_title)
            if res:
                topic_title =  topic_title.replace(res[0], '').strip()
                if self.operation == 'xmind_to_zentao':
                    self.remove_testcase_from_zentao(topic_title, case_path)
                return

            priority = self.priority
            for item in topic_markers:
                if 'priority-' in item:
                    priority = int('%s' % item.lstrip('priority-'))
                    break
            testcase_data = {}
            testcase_data['name'] = topic_title
            testcase_data['case_path'] = case_path
            testcase_data['priority'] = priority
            if not stages and not common_stages:
                stages = self.default_execution_stages
            elif not stages and common_stages:
                stages = common_stages
            testcase_data['execution_stages'] = stages
            testcase_data['case_type'] = self.default_testcase_type
            testcase_data['preconditions'] = common_pre_conditions + preconditions_list
            if common_notes:
                testcase_data['steps'] = [{'action': '备注:' + '，'.join(common_notes), 'expections': []}] + testcase_steps
            else:
                testcase_data['steps'] = testcase_steps
            testcase_data['status'] = self.default_testcase_status
            testcase_data['desc'] = topic_comment

            if self.operation == 'xmind_to_excel':
                self.save_testcase_to_excel(testcase_data, excel_sheet)
            elif self.operation == 'xmind_to_zentao':
                logger.info('正在保存用例到禅道，用例名称：%s' % topic_title)
                self.save_testcase_to_zentao(testcase_data)
                logger.info('操作完成')

        else:  # 遇到的主题为测试集
            if father_topic_id: # 如果存在上级id
                topic_id_path_map[topic_id] = topic_id_path_map[father_topic_id] + '/' + topic_title.rstrip('/')
            else:
                if topic_title:
                    topic_id_path_map[topic_id] = '/' + root_suite_path
                else:
                    topic_id_path_map[topic_id] = ''

            common_pre_conditions = []
            common_notes = []
            common_stages = []
            for sub_topic_data in sub_topic_data_list:
                topic_title = (sub_topic_data.get('title') or '').strip()
                
                node_title = self.remove_digit_no_prefix(topic_title)
                if node_title.startswith('前置条件') or node_title.startswith('前提条件') or node_title.startswith(
                        '前提'):
                    common_pre_conditions.extend(self.parse_pre_conditions_nodes(node_title, sub_topic_data.get('topics')))
                    continue
                
                if node_title.startswith('适用阶段'):
                    if node_title[4:].startswith(':') or node_title[4:].startswith('：'):
                        common_stages = node_title[5:]
                    else:
                        common_stages = node_title[4:]
                    common_stages = common_stages.strip().replace(' ', '').replace('，', ',')
                    continue
           
                if node_title.startswith('备注') or node_title.startswith('公共备注'):
                    common_notes.extend(self.parse_remark_nodes(node_title, sub_topic_data.get('topics')))
                    continue
                self.parse_xmind_sheet_testcases(sub_topic_data, root_suite_path, topic_id_path_map, excel_sheet, topic_id, common_pre_conditions, common_notes, common_stages)


    def format_preconditions(self, preconditions):
        '''格式化前置条件'''

        result = ''
        if len(preconditions) == 1:
            return preconditions[0]

        for idx, precondition in enumerate(preconditions):
            result += f'{idx + 1}.{precondition}\n'
        return result.rstrip('\n')

    def save_testcase_to_excel(self, testcase_data, excel_sheet):
        '''保存用例到excel'''

        preconditions = self.format_preconditions(testcase_data.get('preconditions'))

        actions = ''
        expections = ''

        for idx, step in enumerate(testcase_data.get('steps')):
            actions += f'{idx + 1}.'  + step['action'] + '\n'

            if len(step['expections']) == 1:
                expections += f'{idx + 1}.' + step['expections'][0] + '\n'
            else:
                action_expections = ''
                for idx2, expection in enumerate(step['expections']):
                    action_expections += f'{idx2 + 1}) ' + expection + '\n'
                action_expections = action_expections.rstrip('\n')
                expections += f'{idx + 1}. {action_expections}\n'

        actions = actions.rstrip('\n')
        expections = expections.rstrip('\n')
        case_path = testcase_data.get('case_path')
        testcase_name = testcase_data.get('name')
        remain_path  = '' # 去掉用例路径去掉在禅道的所属模块后的剩余路径
        if self.testcase_module_adapt_to_zen:
            result = self.case_module_id_pattern.findall(case_path)
            if result:
                last_item = result[-1]
                index = case_path.index(last_item)
                case_module_path = case_path[:index]
                for item in result[:-1]:
                    case_module_path = case_module_path.replace(item, '')
                case_module_path = case_module_path + last_item
                case_path, remain_path = case_module_path, case_path[index+len(last_item):].replace('/','-').lstrip('-')
        if remain_path:
           testcase_name = remain_path + '-' +  testcase_data.get('name')

        stages = []
        for stage in testcase_data.get('execution_stages').split(','):
            stages.append(CASE_STAGE_ALIAS_MAP.get(stage, stage))
        stages = list(set(stages))
        stages = '\n'.join(stages)

        row = [case_path, # 所属模块
               '', # 相关研发需求
               testcase_name, # 用例标题
               preconditions, # 前置条件
               actions, # 步骤
               expections, # 预期
               '', # 关键词
               testcase_data.get('priority'), # 优先级
               testcase_data.get('case_type'), # 用例类型
               stages, # 适用阶段
               testcase_data.get('status') # 用例状态
               ]
        excel_sheet.append(row)

    def save_testcase_to_zentao(self, testcase_data):
        '''保存用例到禅道'''

        preconditions = self.format_preconditions(testcase_data.get('preconditions'))
        case_steps = testcase_data.get('steps')

        case_path = testcase_data.get('case_path')
        result = self.case_module_id_pattern.findall(case_path)
        for item in result:
            case_path = case_path.replace(item, '')

        last_module_id, product_id = self.zentao_cli.create_module_by_module_path(case_path)

        testcase_name = testcase_data.get('name')
        case_type = testcase_data.get('case_type')
        case_type = CASE_TYPE_MAP.get(case_type, 'feature')

        stages = []
        for stage in testcase_data.get('execution_stages').split(','):
            stages.append(CASE_STAGE_MAP.get(stage, 'feature'))

        stages = list(set(stages))
        stages = ',' + ','.join(stages)

        self.zentao_cli.create_testcase(product_id,
                                        last_module_id,
                                        testcase_name,
                                        preconditions,
                                        testcase_data.get('priority'),
                                        case_type,
                                        stages,
                                        case_steps
                                        )


    def remove_testcase_from_zentao(self, testcase_name, testcase_module_path):
        '''删除禅道用例'''

        result = self.case_module_id_pattern.findall(testcase_module_path)
        for item in result:
            testcase_module_path = testcase_module_path.replace(item, '')

        last_module_id, product_id = self.zentao_cli.create_module_by_module_path(testcase_module_path)
        self.zentao_cli.remove_testcase(product_id,
                                        last_module_id,
                                        testcase_name
                                        )
    def read_excel_testcase_file(self, file_path, xmind_sheet, xmind_node_path_map):
        '''读取并解析excel用例文件'''

        logger.info(f'正在读取excel测试用例文件：{file_path}')

        work_book = load_workbook(file_path)
        for sheet in work_book:
            # sheet_name  = sheet.title
            first_row = True
            is_testcase_sheet = False
            header_idx_map = {}
            for row in sheet.rows:
                if first_row:
                    for idx, cell in enumerate(row):
                        if cell.value == '用例标题':
                            is_testcase_sheet = True
                        header_idx_map[str(cell.value).strip()] = idx
                    if not is_testcase_sheet:
                        break
                    first_row = False
                    continue


                row_data = []
                for cell in row:
                    row_data.append(str(cell.value or '').strip())

                product_name = row_data[header_idx_map.get('所属产品')]
                result = self.case_module_id_pattern.findall(product_name)
                if result:
                    index = product_name.index(result[0])
                    product_name = product_name[:index]
                if not xmind_sheet.getTitle():
                    xmind_sheet.setTitle(product_name)

                module_path = row_data[header_idx_map.get('所属模块')]
                result = self.case_module_id_pattern.findall(module_path)
                if result:
                    index = module_path.index(result[0])
                    module_path = module_path[:index]

                testcase_name = row_data[header_idx_map.get('用例标题')]
                preconditions = row_data[header_idx_map.get('前置条件')]
                case_steps = row_data[header_idx_map.get('步骤')]
                expections = row_data[header_idx_map.get('预期')]
                priority = row_data[header_idx_map.get('优先级')]
                case_type = row_data[header_idx_map.get('用例类型')]
                suitable_stage = row_data[header_idx_map.get('适用阶段')].replace('\n', ',')

                logger.info(f'正在保存用例到xmind文件，用例名称：{testcase_name}')
                self.save_testcase_to_xmind(xmind_sheet,
                                            xmind_node_path_map,
                                            product_name,
                                            module_path,
                                            testcase_name,
                                            preconditions,
                                            case_steps,
                                            expections,
                                            priority,
                                            case_type,
                                            suitable_stage)
                logger.info('保存完成')

    def create_xmind_moudle_node_by_module_path(self, product_name, case_module_path, xmind_node_path_map):
        '''
        根据测试用例模块路径批量创建与模块对应的的xmind 节点
        '''

        parent_path =f'/{product_name}'
        for module_name in case_module_path.strip('/').split('/'):
            module_path = f'{parent_path}/{module_name}'

            if module_path not in xmind_node_path_map:
                node = xmind_node_path_map[parent_path].addSubTopic()
                xmind_node_path_map[module_path] = node
                node.setTitle(module_name)
            parent_path = module_path


    def split_testcase_steps_from_excel(self, val):
        '''拆解禅道EXCEL用例中的步骤和预期值
        :param val: 步骤动作或者预期
        '''

        result_list = []
        lines = []
        for line in val.split('\n'):
            if self.zentao_excel_case_digit_no_pattern.findall(line):
                if lines:
                    result_list.append(lines)
                    lines = []
                lines.append(self.remove_digit_no_prefix(line).strip())
            else:
                lines.append(line.strip())
        if lines:
            result_list.append(lines)

        return result_list

    def save_testcase_to_xmind(self, xmind_sheet,
                               xmind_node_path_map,
                               product_name,
                               module_path,
                               testcase_name,
                               preconditions,
                               actions,
                               expections,
                               priority,
                               case_type,
                               suitable_stage):
        '''保存用例到xmind画布'''

        root_path = f'/{product_name}'
        if root_path not in xmind_node_path_map:
            root_topic = xmind_sheet.getRootTopic()
            xmind_node_path_map[root_path] = root_topic
            root_topic.setTitle(product_name)
        self.create_xmind_moudle_node_by_module_path(product_name, module_path, xmind_node_path_map)
        module_path = module_path.strip('/')
        case_module_path = f'{root_path}/{module_path}'
        case_path = f'{case_module_path}/{testcase_name}'
        if case_path not in xmind_node_path_map:
            case_topic = xmind_node_path_map[case_module_path].addSubTopic()
            case_topic.setTitle(testcase_name)
            xmind_node_path_map[case_path] = case_topic
        else:
            case_topic = xmind_node_path_map[case_path]

        case_topic.addMarker("priority-" + priority)
        stage_topic = case_topic.addSubTopic()
        stage_topic.setTitle(f'适用阶段: {suitable_stage}')

        if preconditions:
            preconditions = preconditions.split('/n')
            if len(preconditions) == 1:
                sub_topic = case_topic.addSubTopic()
                sub_topic.setTitle(f'前提条件: {preconditions[0]}')
            else:
                precondition_topic = case_topic.addSubTopic()
                precondition_topic.setTitle('前提条件')
                for precondition in preconditions:
                    sub_topic = precondition_topic.addSubTopic()
                    sub_topic.setTitle(precondition)

        actions = self.split_testcase_steps_from_excel(actions)
        expections = self.split_testcase_steps_from_excel(expections)
        for action_list, expections_list in zip(actions, expections):
            action = ''.join(action_list).strip()

            temp = ''.join(expections_list).strip()
            if temp:
                action = action or '步骤略'
            elif not action:
                continue

            action_topic = case_topic.addSubTopic()
            action_topic.setTitle(action)

            for expection in expections_list:
                if expection == '1':
                    continue
                expection_topic = action_topic.addSubTopic()
                expection_topic.setTitle(expection)

